/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package chatclient;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

/**
 *
 * @author NAMQUYUIT
 */
public class ChatClient {

    public static final int PORT_ACCOUNT = 2114;
    public static final int BUFFER = 1024 * 8;
    public static final String MESSAGE_NAME_REGISTRY = "NAME_REGISTRY";
    public static final String MESSAGE_NAME_REGISTRY_REJECTED = "NAME_REGISTRY_REJECTED";
    public static final String MESSAGE_NAME_REGISTRY_ACCEPTED = "NAME_REGISTRY_ACCEPTED";
    public static final String MESSAGE_ACCOUNT_LOGIN = "ACCOUNT_LOGIN";
    public static final String MESSAGE_ACCOUNT_LOGIN_FAILED = "ACCOUNT_LOGIN_FAILED";
    public static final String MESSAGE_ACCOUNT_LOGIN_SUCCESSED = "ACCOUNT_LOGIN_SUCCESSED";
    public static final String MESSAGE_ACCOUNT_HAS_NEW_USER_LOGIN = "ACCOUNT_HAS_NEW_USER_LOGIN";
    public static final String MESSAGE_FILE_SEND_ASK = "FILE_SEND_ASK";
    public static final String MESSAGE_FILE_SEND_ACCEPT = "FILE_SEND_ACCEPT";
    public static final String MESSAGE_FILE_SEND = "FILE_SEND";
    //public static final String MESSAGE_TO_SERVER = "CLIENT_SERVER_MESSAGE";
    public static final String MESSAGE_TO_CLIENT = "CLIENT_CLIENT_MESSAGE";
    public static final String MESSAGE_CREATE_GROUP = "CREATE_GROUP";
    public static final String MESSAGE_CREATE_GROUP_SUCCESS = "CREATE_GROUP_SUCCESS";
    public static final String MESSAGE_GROUP_ADD_USER = "GROUP_ADD_USER";
    public static final String MESSAGE_GROUP_LEAVE = "GROUP_LEAVE";
    public static final String MESSAGE_GROUP_REMOVE = "GROUP_REMOVE";
    public static final String MESSAGE_GROUP_SEND_MESSAGE = "GROUP_SEND_MESSAGE";
    public static final String MESSAGE_GROUP_FILE_SEND_ASK = "FILE_GROUP_SEND_ASK";
    public static final String MESSAGE_GROUP_FILE_SEND_ACCEPT = "FILE_GROUP_SEND_ACCEPT";
    public static final String MESSAGE_GROUP_FILE_SEND = "FILE_GROUP_SEND";
    private IOnTransferListener onTransferListener;
    private IOnRegistryListener onRegistryListener;
    private IOnGroupListener onGroupListener;
    private IOnLoginListener onLoginListener;
    private ArrayList<String> listOtherUsers;
    private String name;
    private Socket socket;
    private BufferedReader input;
    private PrintWriter output;
    
    public static String ServerAddress = "";
    public static boolean isConnected = false;

    public ChatClient() {
        try {
            this.name = null;
            this.onTransferListener = null;
            this.onRegistryListener = null;
            this.onGroupListener = null;
            this.onLoginListener = null;
            this.listOtherUsers = new ArrayList<>();
            this.socket = new Socket(ServerAddress, PORT_ACCOUNT);
            this.input = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
            this.output = new PrintWriter(this.socket.getOutputStream(), true);
            isConnected = true;
        } catch (UnknownHostException ex) {
        } catch (IOException ex) {
        }
    }

    public String getUsername() {
        return name;
    }
    
    public void setOnTransferListener(IOnTransferListener listener) {
        this.onTransferListener = listener;
    }
    
    public void setOnRegistryListener(IOnRegistryListener listener) {
        this.onRegistryListener = listener;
    }
    
    public void setOnLoginListener(IOnLoginListener listener) {
        this.onLoginListener = listener;
    }
    
    public void setOnGroupListener(IOnGroupListener listener) {
        this.onGroupListener = listener;
    }
    
    /*
    public boolean registry(String identify) {
        if (this.name == null) {
            this.output.println(MESSAGE_NAME_REGISTRY + "::" + identify);
            return true;
        }
        return false;
    }*/
    public boolean registry(String username, String password) {
        if (this.output != null) {
            this.output.println(MESSAGE_NAME_REGISTRY + "::" + username + "::" + password);
            return true;
        }
        return false;
    }
    
    public boolean login(String username, String password) {
        if (this.output != null) {
            this.output.println(MESSAGE_ACCOUNT_LOGIN + "::" + username + "::" + password);
            return true;
        }
        return false;
    }
    
    public String sendMessage(String otherName, String message) {
        if (this.output != null) {
            this.output.println(MESSAGE_TO_CLIENT + "::" + otherName + "::" + message);
            /*if (this.onTransferListener != null) {
                this.onTransferListener.onSendMessage(this.name + "::" + message);
            }*/
            return message;
        } 
        return null;
    }
    
    /*
    public boolean sendFile(String otherName, String pathFile) {
        if (this.socket != null) {
            
            try {
                String fileName;
                
                File myFile = new File(pathFile);
                fileName = myFile.getName();
                this.output.println(MESSAGE_FILE_SEND + "::" + otherName + "::" + fileName);
                
                byte[] byteArray = new byte[(int) myFile.length()];
                BufferedInputStream bis = new BufferedInputStream(new FileInputStream(myFile));
                int bytesRead = bis.read(byteArray, 0, byteArray.length);
                OutputStream os = this.socket.getOutputStream();
                os.write(byteArray, 0, bytesRead);
                os.flush();
                
                return true;
            } catch (IOException ex) {
                
            }
        }
        return false;
    }
    * */
    
    public boolean wantSendFile(String otherName, String pathFile) {
        if (this.socket != null) {
            this.output.println(MESSAGE_FILE_SEND_ASK + "::" + otherName + "::" + pathFile);
            return true;
        }
        return false;
    }
    
    public boolean sendFile(String otherName, String pathFile, String pathSaveFile) {
        if (this.socket != null) {
            
            try {
                String fileName;
                
                File myFile = new File(pathFile);
                fileName = myFile.getName();
                this.output.println(MESSAGE_FILE_SEND + "::" + otherName + "::" + fileName + "::" + myFile.length() + "::" + pathSaveFile);
                
                byte[] byteArray = new byte[BUFFER];
                BufferedInputStream bis = new BufferedInputStream(new FileInputStream(myFile));
                OutputStream os = this.socket.getOutputStream();
                int bytesRead;
                while ((bytesRead = bis.read(byteArray, 0, byteArray.length)) > 0) {
                    os.write(byteArray, 0, bytesRead);
                    os.flush();
                }
                bis.close();
                
                return true;
            } catch (IOException ex) {
                
            }
        }
        return false;
    }
    
    public boolean createGroup(String[] lstIdentities)
    {
        if (this.name != null && lstIdentities != null && lstIdentities.length > 0) {
            this.output.println(MESSAGE_CREATE_GROUP);
            StringBuilder builder = new StringBuilder();
            for (String user : lstIdentities) {
                builder.append(user);
                builder.append("::");
            }
            //builder.deleteCharAt(builder.length()-1);
            builder.delete(builder.length()-2, builder.length());
            this.output.println(builder.toString());
            
            return true;
        } 
        return false;
    }
    
    // TODO: delete
    public boolean createGroup() {
        if (this.name != null) {
            this.output.println(MESSAGE_CREATE_GROUP);
            return true;
        } 
        return false;
    }
    
    public boolean addUserToGroup(String groupId, String username) {
        if (this.name != null) {
            this.output.println(MESSAGE_GROUP_ADD_USER + "::" + groupId + "::" + this.name + "::" + username);
            return true;
        } 
        return false;
    }
    
    public boolean leaveFromGroup(String groupId) {
        if (this.name != null) {
            this.output.println(MESSAGE_GROUP_LEAVE + "::" + groupId);
            return true;
        } 
        return false;
    }
    
    public boolean removeFromGroup(String groupId, String username) {
        if (this.name != null) {
            this.output.println(MESSAGE_GROUP_REMOVE + "::" + groupId + "::" + username);
            return true;
        } 
        return false;
    }
    
    public boolean sendMessageToGroup(String groupId, String message) {
        if (this.name != null) {
            this.output.println(MESSAGE_GROUP_SEND_MESSAGE + "::" + groupId + "::" + this.name + "::" + message);
            return true;
        } 
        return false;
    }
    
    public boolean wantSendFileToGroup(String groupId, String pathFile) {
        if (this.socket != null) {
            this.output.println(MESSAGE_GROUP_FILE_SEND_ASK + "::" + groupId + "::" + pathFile);
            return true;
        }
        return false;
    }
    
    public boolean sendFileToGroup(String groupId, String otherName, String pathFile, String pathSaveFile) {
        if (this.socket != null) {
            
            try {
                String fileName;
                
                File myFile = new File(pathFile);
                fileName = myFile.getName();
                this.output.println(MESSAGE_GROUP_FILE_SEND + "::" + groupId + "::" + otherName + "::" + fileName + "::" + myFile.length() + "::" + pathSaveFile);
                
                byte[] byteArray = new byte[BUFFER];
                BufferedInputStream bis = new BufferedInputStream(new FileInputStream(myFile));
                OutputStream os = this.socket.getOutputStream();
                int bytesRead;
                while ((bytesRead = bis.read(byteArray, 0, byteArray.length)) > 0) {
                    os.write(byteArray, 0, bytesRead);
                    os.flush();
                }
                bis.close();
                
                return true;
            } catch (IOException ex) {
                
            }
        }
        return false;
    }
    /*
    private boolean saveFile(String pathFile, byte[] byteArray, int bytesRead) {
        if (this.socket != null) {
            try {
                FileOutputStream fos = new FileOutputStream(pathFile);
                BufferedOutputStream bos = new BufferedOutputStream(fos);
                bos.write(byteArray, 0, bytesRead);
                bos.close();
                return true;
            } catch (IOException ex) {
                // do nothing
            }
        }
        return false;
    }
    * */

    public void run() {
        String command;

        // registry
        while (true) {
            try {
                String[] tmpArr;
                
                command = this.input.readLine();
                tmpArr = command.split("::");
                if (command.contains(MESSAGE_NAME_REGISTRY_ACCEPTED)) {
                    this.name = tmpArr[1];
                    
                    // add list of current available users
                    command = this.input.readLine();
                    if (!command.isEmpty()) {
                        tmpArr = command.split("::");
                        for (String string : tmpArr) {
                            this.listOtherUsers.add(string);
                        }
                    }
                    
                    HashMap<String, ArrayList<String>> listUnreadMessages = new HashMap<>();
                    command = this.input.readLine();
                    if (!command.isEmpty()) {
                        tmpArr = command.split("::");
                        for (int i = 0; i < tmpArr.length-1; i+=2) {
                            if (!listUnreadMessages.containsKey(tmpArr[i])) {
                                ArrayList<String> tmpArrMessages = new ArrayList<>();
                                tmpArrMessages.add(tmpArr[i+1]);
                                listUnreadMessages.put(tmpArr[i], tmpArrMessages);
                            } else {
                                listUnreadMessages.get(tmpArr[i]).add(tmpArr[i+1]);
                            }
                        }
                    }
                    
                    if (this.onRegistryListener != null) {
                        this.onRegistryListener.onRegistrySuccess("You has registried successful with the username: " + this.name);
                    }
                    if (this.onLoginListener != null) {
                        this.onLoginListener.onLoginSuccess(this.listOtherUsers, listUnreadMessages);
                    }
                    break;
                } else if (command.contains(MESSAGE_NAME_REGISTRY_REJECTED)) {
                    String tmpName = tmpArr[1];
                    if (this.onRegistryListener != null) {
                        this.onRegistryListener.onRegistryFailed("The " + tmpName + " has already existed in the server.");
                    }
                } else if (command.contains(MESSAGE_ACCOUNT_LOGIN_SUCCESSED)) {
                    this.name = tmpArr[1];
                    
                    // add list of current available users
                    command = this.input.readLine();
                    tmpArr = command.split("::");
                    for (String string : tmpArr) {
                        this.listOtherUsers.add(string);
                    }
                    
                    HashMap<String, ArrayList<String>> listUnreadMessages = new HashMap<>();
                    command = this.input.readLine();
                    if (!command.isEmpty()) {
                        tmpArr = command.split("::");
                        for (int i = 0; i < tmpArr.length-1; i+=2) {
                            if (!listUnreadMessages.containsKey(tmpArr[i])) {
                                ArrayList<String> tmpArrMessages = new ArrayList<>();
                                tmpArrMessages.add(tmpArr[i+1]);
                                listUnreadMessages.put(tmpArr[i], tmpArrMessages);
                            } else {
                                listUnreadMessages.get(tmpArr[i]).add(tmpArr[i+1]);
                            }
                        }
                    }
                    
                    if (this.onLoginListener != null) {
                        this.onLoginListener.onLoginSuccess(listOtherUsers, listUnreadMessages);
                    }
                    break;
                } else if (command.contains(MESSAGE_ACCOUNT_LOGIN_FAILED)) {
                    if (this.onLoginListener != null) {
                        this.onLoginListener.onLoginFailed("The username or password incorrect.");   
                    }
                }
            } catch (IOException ex) {
            }
        }

        // running
        while (true) {
            try {
                command = this.input.readLine();
                if (command != null && command.length() > 0) {
                    String[] tmpArr = command.split("::");
                    if (command.contains(MESSAGE_ACCOUNT_HAS_NEW_USER_LOGIN)) {
                        this.onLoginListener.onHasNewUser(tmpArr[1]);
                    } else if (command.contains(MESSAGE_FILE_SEND_ASK)) {
                        if (this.onTransferListener != null) {
                            String path = this.onTransferListener.onNewSendFileSignal(tmpArr[1], tmpArr[2]);
                            if (path != null && path.length() > 0) {
                                this.output.println(MESSAGE_FILE_SEND_ACCEPT + "::" + tmpArr[1] + "::" + tmpArr[2] + "::" + path);
                            }
                        }
                    } else if (command.contains(MESSAGE_FILE_SEND_ACCEPT)) {
                        this.sendFile(tmpArr[1], tmpArr[2], tmpArr[3]);
                    } else if (command.contains(MESSAGE_FILE_SEND)) {
                        int length = Integer.valueOf(tmpArr[3]);
                        String pathFile = tmpArr[4];
                        /*
                        String fileName = "hehe" + tmpArr[2];
                        String workingDir = System.getProperty("user.dir");
                        String pathFile = workingDir + "\\" + fileName;*/
                        FileOutputStream fos = new FileOutputStream(pathFile);
                        BufferedOutputStream bos = new BufferedOutputStream(fos);
                        
                        byte[] byteArray = new byte[BUFFER];
                        InputStream is = this.socket.getInputStream();
                        int count = 0;
                        int bytesRead;
                        
                        while (count < length) {
                            bytesRead = is.read(byteArray, 0, byteArray.length);
                            if (bytesRead > 0) {
                                count += bytesRead;
                                bos.write(byteArray, 0, bytesRead);
                            }
                        }
                        bos.close();
                        
                        if (this.onTransferListener != null) {
                            this.onTransferListener.onSendFileSuccess(tmpArr[1], pathFile);
                        }
                    } else if (command.contains(MESSAGE_CREATE_GROUP_SUCCESS)) {
                        if (this.onGroupListener != null) { 
                            String[] tmpUsers = new String[tmpArr.length-4];
                            int count = 0;
                            for (int i = 3; i < tmpArr.length; i++) {
                                if (this.name.equals(tmpArr[i]) == false) {
                                    tmpUsers[count++] = tmpArr[i];
                                }
                            }
                            this.onGroupListener.onCreateGroupSuccess(tmpArr[1], tmpUsers);
                        }
                    } else if (command.contains(MESSAGE_GROUP_ADD_USER)) {
                        if (this.onGroupListener != null) { 
                            String[] tmpArrUsers = new String[tmpArr.length-4];
                            for (int i = 4; i < tmpArr.length; i++) {
                                tmpArrUsers[i-4] = tmpArr[i];
                            }
                            this.onGroupListener.onAddNewUserToGroup(tmpArr[1], tmpArr[2], tmpArr[3], tmpArrUsers);
                        }
                    } else if (command.contains(MESSAGE_GROUP_SEND_MESSAGE)) {
                        if (this.onGroupListener != null) { 
                            this.onGroupListener.onGroupNewMessage(tmpArr[2], tmpArr[1], tmpArr[3]);
                        }
                    } else if (command.contains(MESSAGE_GROUP_LEAVE)) {
                        if (this.onGroupListener != null) { 
                            this.onGroupListener.onUserLeaveFromGroup(tmpArr[1], tmpArr[2]);
                        }
                    } else if (command.contains(MESSAGE_GROUP_REMOVE)) {
                        if (this.onGroupListener != null) { 
                            this.onGroupListener.onUserRemoveFromGroup(tmpArr[1], tmpArr[2], tmpArr[3]);
                        }
                    } else if (command.contains(MESSAGE_TO_CLIENT)) {
                        if (this.onTransferListener != null) {
                            this.onTransferListener.onNewMessage(tmpArr[1], tmpArr[2]);
                        }
                    }
                    //System.out.println(tmpArr[0] + " : " + tmpArr[1]);
                } else {
                }
            } catch (IOException ex) {
            }
        }
    }
    
    
    private static ChatClient instance=null;
    
    public static ChatClient getInstance(){
        if(instance == null){
            instance=new ChatClient();
            //instance.run();
        }
        return instance;
    }
    
    public static void dispose() {
        instance = null;
    }
    
    public static boolean isConnected() {
        return isConnected;
    }
}
